# 1.LeNet由Yann Lecun 提出，是现代卷积神经网络的起源之一。Yann将该网络用于邮局的邮政的邮政编码识别，有着良好的学习和识别能力，接下来利用该网络的这样功能，完成下列预测模型。
# (1)导入图像显示模块
import matplotlib.pyplot as plt
# 1.按照要求，使用mnist数据集完成cnn处理
from keras.datasets.fashion_mnist import load_data
from keras import Sequential, layers, activations, optimizers, losses, metrics, Model
from keras.src.utils import to_categorical

# ①获取数据
# 1)获取数据信息
# 2)切分数据集
(x_train, y_train), (x_test, y_test) = load_data()
print()
# 3)对数据进行预处理工作
x_train = x_train.reshape(-1, 28, 28, 1) / 255
x_test = x_test.reshape(-1, 28, 28, 1) / 255

onehot_dim = len(set(y_train))
# 独热编码标签
y_train = to_categorical(y_train, num_classes=onehot_dim)  # 指定类别总数为10
y_test = to_categorical(y_test, num_classes=onehot_dim)  # ②模型处理


# (7)定义Lenet5模型
# (8)创建名为Lenet5的模型类并继承自Model类
class Lenet5(Model):
    # (9)定义Lenet5模型的初始化方法，*args和**kwargs分别用于接收任意数量的位置参数和关键字参数
    # (10)调用父类Model的初始化方法
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # (11)创建序贯模型实例self.conv，用于存储卷积层和池化层
        self.conv = Sequential([
            # (12)添加一个卷积层，6个卷积核，核大小为(5, 5)
            # (13)添加一个最大池化层
            layers.Conv2D(filters=6, kernel_size=(5, 5), activation=activations.relu),
            layers.MaxPooling2D(),
            # (14)添加一个卷积层，16个卷积核，核大小为(5, 5)
            # (15)添加一个最大池化层
            layers.Conv2D(filters=16, kernel_size=(5, 5), activation=activations.relu),
            layers.MaxPooling2D(),
        ])

        # (16)创建序贯模型实例self.flat，用于存储展平层
        self.flat = layers.Flatten()

        # (17)创建序贯模型实例self.fc，用于存储全连接层
        self.fc = Sequential([
            # (18)添加一个全连接层，神经元数量为120，激活函数为relu
            # (19)添加一个丢弃层，丢弃比例为0.3
            layers.Dense(units=120, activation=activations.relu),
            layers.Dropout(0.3),
            # (20)添加一个全连接层，神经元数量为84，激活函数为relu
            # (21)添加一个丢弃层，丢弃比例为0.3
            layers.Dense(units=84, activation=activations.relu),
            layers.Dropout(0.3),
            # (22)添加一个全连接层，神经元数量为1，激活函数为sigmoid，为输出层
            layers.Dense(units=onehot_dim, activation=activations.softmax)
        ])

    # (23)定义call方法，用于模型的 前向传播
    def call(self, inputs, training=None, mask=None):
        # (24)将输入数据通过卷积层和池化层处理
        out = self.conv(inputs)
        # (25)将处理后的数据展平
        out = self.flat(out)
        # (26)将展平后的数据全连接层处理
        out = self.fc(out)
        # (27)返回处理后的结果
        return out


# (28)创建Lenet5模型的实例
model = Lenet5()

# (29)指定模型的输入形状
model.build(input_shape=(None, 28, 28, 1))

# (30)打印模型的概况信息
model.summary()

# (31)编译模型，选择合适的 优化器、损失函数和评估指标训练模型
model.compile(optimizer=optimizers.Adam(), loss=losses.categorical_crossentropy,
              metrics=[metrics.CategoricalAccuracy(), metrics.Precision(), metrics.Recall(), metrics.F1Score()])

# (32)训练模型，同时传入验证集数据
log = model.fit(x_train, y_train, epochs=10, batch_size=100, validation_data=(x_test, y_test))

model.evaluate(x_test, y_test)
# (33)获取训练集的准确率历史数据
train_acc = log.history['categorical_accuracy']

# (34)获取验证集的准确率历史数据
val_acc = log.history['val_categorical_accuracy']

# (35)绘制训练集准确率曲线，颜色为红色
plt.plot(train_acc, color='r')

# (36)绘制验证集准确率曲线，颜色为绿色
plt.plot(val_acc, color='g')

# (37)展示绘制的准确率曲线图
plt.show()
